package sistema;

import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import mensagem.EmailMessage;
import mensagem.Mensagem;
import mensagem.SMSMessage;
import conta.ContaUserStories;
import excecoes.ContaException;
import excecoes.IPException;
import excecoes.LocalizacaoException;
import excecoes.MensagemException;
import excecoes.PessoaException;
import excecoes.SistemaException;
import excecoes.UsuarioException;
import geoip.Location;
import geoip.LookupService;

/**
 * Sistema Principal do software.
 * 
 * @author Adalberto Teixeira
 * @author Andrey Nogueira
 * @author Laise Florentino
 * @author Laio Francis
 * 
 */
public class Sistema {
	ArrayList<ContaUserStories> contas;
	ArrayList<String> online;
	String mensageiro;
	ServicoMensagemInstantanea novoChat;
	manipuladorXML contasXML;
	manipuladorTXT contasTXT;
	String gabaritos;
	int portaChat;

	/**
	 * Construtor de Sistema.
	 */
	public Sistema() {
	
		online = new ArrayList<String>();
		contas = new ArrayList<ContaUserStories>();

		mensageiro = null;
		contasXML = (manipuladorXML) manipuladorXML.getInstance();
		contasTXT = (manipuladorTXT) manipuladorTXT.getInstance();
		try {
			for (ContaUserStories conta : (ArrayList<ContaUserStories>) contasXML
					.carregarContas("contas.xml")) {
				contas.add(conta);
			}
		} catch (Exception e) {
			//nao faz nada
		}
		try {
			for (ContaUserStories conta : (ArrayList<ContaUserStories>) contasTXT
			.carregarContas("contas.txt")) {
				contas.add(conta);
			}
		} catch (Exception e){
			//nao faz nada
		}
	}

	/**
	 * Cria conta de usuario.
	 * 
	 * @param userName login do usuario
	 * @param nome nome do usuario
	 * @param email email do usuario
	 * @param senha senha do usuario
	 * @param telefone telefone usuario
	 * 
	 * @throws UsuarioException 
	 * @throws PessoaException 
	 * 
	 */
	public void criarUsuario(String userName, String nome, String email,
			String senha, String telefone, boolean exConta) throws UsuarioException, PessoaException {
		ContaUserStories conta = new ContaUserStories(userName, nome, email,
				senha, telefone, "", "");
		if (exConta)
			contas.add((ContaUserStories) conta);
	}
	
	/**
	 * sobrecarga do metodo
	 */

	public void criarUsuario(String userName, String nome, String email,
			String senha, String telefone) throws UsuarioException, PessoaException {
		ContaUserStories conta = new ContaUserStories(userName, nome, email,
				senha, telefone, "", "");
		if (this.UserNameExiste(userName))
			throw new UsuarioException("O username jah existe.");
		contas.add((ContaUserStories) conta);
	}
	/**
	 * Autentica usuario no sistema.
	 * 
	 * @param userName
	 *            login do usuario
	 * @param senha
	 *            senha do usuario
	 * @param ip
	 *            da maquina do usuario
	 *            
	 * @throws SistemaException           
	 * @throws ContaException
	 * @throws IPException
	 */
	public String login(String userName, String senha, String ip)
			throws SistemaException, ContaException, IPException {
		int id;
		this.testaIP(ip);
		if (this.UserNameExiste(userName)) {
			id = this.getIdPorUserName(userName);
			if (contas.get(id).recolher("senha").equals(senha)) {
				contas.get(id).setIp(ip);
				if (!online.contains(userName))
					online.add(userName);
				return userName;

			} else
				throw new SistemaException("Login/senha invalidos.");
		}
		
		throw new SistemaException("Login/senha invalidos.");
	}

	/**
	 * Testa se o IP eh valido.
	 * 
	 * @param ip
	 * 
	 * @throws IPException
	 */
	private void testaIP(String ip) throws IPException {
		char[] caracteresIP = { '0', '1', '2', '3', '4', '5', '6', '7',
				'8', '9', '.' };
		// ip vazio
		if (ip == null || ip.equalsIgnoreCase(""))
			throw new IPException("IP invalido.");
		// fazer a lista
		List<Character> lista = new ArrayList<Character>();
		for (int i = 0; i < caracteresIP.length; i++)
			lista.add(caracteresIP[i]);
		// confere formato ip
		char[] confere = ip.toCharArray();
		for (int i = 0; i < confere.length; i++)
			if (!lista.contains(confere[i]))
				throw new IPException("IP invalido.");
		// ip tem somente 3 pontos
		int quantidadeDePontos = 0;
		for (int i = 0; i < confere.length; i++)
			if (confere[i] == '.')
				quantidadeDePontos++;
		if (quantidadeDePontos != 3)
			throw new IPException("IP invalido.");
		// ip tem que ter formato numeros.numeros.numeros.numeros
		// nao existe .0.0.0
		if (ip.charAt(0) == '.') {
			throw new IPException("IP invalido.");
		}
		// nao exite 0.0.0.
		if (ip.charAt(ip.length() - 1) == '.')
			throw new IPException("IP invalido.");
		// nao existe 0.0..0
		for (int i = 0; i < ip.length() - 1; i++)
			if (ip.charAt(i) == '.')
				if (ip.charAt(i + 1) == '.')
					throw new IPException("IP invalido.");
	}

	/**
	 * Desloga usuario e salva seus dados.
	 * 
	 * @param usuario
	 * 
	 * @throws SistemaException
	 */
	public void logout(String usuario) throws SistemaException {
		try {
			if (!online.contains(usuario))
				throw new SistemaException("Sessao inexistente.");
			online.remove(usuario);
			contasXML.salvar(contas);
		} catch (Exception e) {
			throw new SistemaException("Sessao inexistente.");
		}
	}

	/**
	 * Apaga todas as contas e logs do sistema.
	 */
	public void zerarSistema() {
		contas.clear();
		online.clear();
	}

	/**
	 * Retorna localizacao do usuario.
	 * 
	 * @return localizacao
	 */
	public String getLocalizadores() {
		return "[GeoIP, Manual]";
	}

	/**
	 * Procura o userName do usuario atraves de seu nome.
	 * 
	 * @param nome nome do usuario
	 * @param indice 
	 * @return userName do usuario
	 * 
	 * @throws ContaException
	 */
	public String getUsuarioPorNome(String nome, String indice)
			throws ContaException {
		int vezes = 1;
		ContaUserStories c;
		Iterator<ContaUserStories> it;
		it = contas.iterator();
		while (it.hasNext()) {
			c = (ContaUserStories) it.next();
			if (c.recolher("nome").contains(nome)) {
				if (Integer.parseInt(indice) == vezes)
					return c.recolher("userName");
				else
					vezes++;
			}
		}
		throw new ContaException("O usuario nao existe.");
	}

	/**
	 * Busca um atributo especifico do usuario.
	 * 
	 * @param userName userName do usuario
	 * @param atributo atributo do usuario que deseja-se ter acesso
	 * @return String com atributo do usuario
	 * 
	 * @throws ContaException
	 */
	public String getAtributoUsuario(String userName, String atributo)
			throws ContaException {
		try {
			int id = this.getIdPorUserName(userName);
			if (atributo.equals("nome"))
				return contas.get(id).recolher("nome");
			if (atributo.equals("telefone"))
				return contas.get(id).recolher("telefone");
			if (atributo.equals("email"))
				return contas.get(id).recolher("email");
			if (atributo.equals("userName"))
				return contas.get(id).recolher("userName");
			if (atributo.equals("senha"))
				return contas.get(id).recolher("senha");
			if (atributo.equals("foto"))
				return contas.get(id).recolher("foto");
		} catch (NullPointerException e) {
			throw new ContaException("O usuario nao existe.");
		}
		return null;
	}

	/**
	 * Procura indice do usuario na lista de contas atraves do userName.
	 * 
	 * @param userName
	 * @return int com indice da conta do usuario na lista de contas
	 */
	private int getIdPorUserName(String userName) {
		ContaUserStories c;
		Iterator<ContaUserStories> it;
		it = contas.iterator();
		while (it.hasNext()) {
			c = (ContaUserStories) it.next();
			if (c.recolher("userName").equals(userName))
				return contas.indexOf(c);
		}
		return -1;
	}

	/**
	 * Verifica se determinado usuario existe.
	 * 
	 * @param userName
	 * @return true se existe, false caso contrario
	 */
	private boolean UserNameExiste(String userName) {
		if (this.getIdPorUserName(userName) != -1) {
			return true;
		}
		return false;
	}

	/**
	 * Atualiza dados do usuario.
	 * 
	 * @param userName
	 * @param atributo atributo que se deseja atualizar
	 * @param valor novo valor do atributo
	 * 
	 * @throws ContaException
	 * @throws IPException 
	 * @throws UsuarioException 
	 * @throws PessoaException 
	 */
	public void atualizarUsuario(String userName, String atributo, String valor)
			throws ContaException, IPException, PessoaException, UsuarioException {
		int id;
		if(atributo.equals("userName"))
			throw new ContaException("Nao eh permitido alterar o username.");
		if(atributo.equals("senha") & valor==null)
			throw new IPException("Senha deve ter no minimo 6 caracteres.");
		try {
			id = this.getIdPorUserName(userName);
			if (contas.get(id).equals(null))
				;
		} catch (Exception e) {
			throw new ContaException("O usuario nao existe.");
		}
		contas.get(id).atualizar(atributo, valor);
	}

	/**
	 * Remove usuario do sistema.
	 * 
	 * @param userName
	 * 
	 * @throws ContaException
	 */
	public void removerUsuario(String userName) throws ContaException {
		try {
			int id = this.getIdPorUserName(userName);
			if (contas.get(id).equals(null))
				throw new ContaException("O usuario nao existe.");
			contas.add(id, null);
		} catch (Exception e) {
			throw new ContaException("O usuario nao existe.");
		}
	}

	/**
	 * Verifica se determinado usuario esta conectado.
	 * 
	 * @param usuario
	 * @return true se esta conectado, false caso contrario
	 */
	public boolean estaConectado(String usuario) {
		try {
			if (online.contains(usuario))
				return true;
			else
				return false;
		} catch (Exception e) {
			return false;
		}
	}

	/**
	 * Cadastra usuario e seu ip no sistema.
	 * 
	 * @param userName
	 * @param nome
	 * @param email
	 * @param senha
	 * @param telefone
	 * @param ip
	 * @return String com userName do usuario cadastrado
	 * 
	 * @throws ContaException
	 * @throws SistemaException
	 * @throws IPException
	 * @throws UsuarioException 
	 * @throws PessoaException 
	 */
	public String cadastrarUsuario(String userName, String nome, String email,
			String senha, String telefone, String ip) throws ContaException,
			SistemaException, IPException, UsuarioException, PessoaException {
		this.criarUsuario(userName, nome, email, senha, telefone);
		return this.login(userName, senha, ip);
	}

	/**
	 * Obtem localizacao automatica do usuario.
	 * 
	 * @param usuario
	 * @return String com a localizacao
	 * 
	 * @throws IPException
	 * @throws IOException
	 * @throws ContaException
	 */
	public String obterLocalizacao(String usuario) throws IPException,
			IOException, ContaException {
		try {
			int id = this.getIdPorUserName(usuario);
			LookupService cl = new LookupService(
					"../../GeoLiteCity/GeoLiteCity.dat",
					LookupService.GEOIP_MEMORY_CACHE);
			try {
				Location l1 = cl.getLocation(contas.get(id).getIP());
				contas.get(id).atualizar("localizacao", Float.toString(l1.latitude),
						Float.toString(l1.longitude));
			} catch (LocalizacaoException e) {
				throw new ContaException("Latitude/Longitude invalidos.");
			} catch (Exception e) {
				throw new IPException("IP invalido.");
			}
			return contas.get(id).recolher("localizacao");
		} catch (Exception e) {
			throw new IPException("Nao foi possivel obter a localizacao.");
		}
	}

	/**
	 * Seta ou atualiza localizacao.
	 * 
	 * @param usuario
	 * @param latitude
	 * @param longitude
	 * 
	 * @throws ContaException
	 * @throws SistemaException
	 * @throws LocalizacaoException 
	 */
	public void setLocalizacao(String usuario, String latitude, String longitude)
			throws ContaException, SistemaException, LocalizacaoException {
		try {
			if (!online.contains(usuario))
				throw new SistemaException("O usuario nao existe.");
			int id = this.getIdPorUserName(usuario);
			contas.get(id).atualizar("localizacao", latitude, longitude);
		} catch (LocalizacaoException e) {
			throw new LocalizacaoException("Latitude/Longitude invalidos.");
		} catch (NullPointerException e) {
			throw new SistemaException("O usuario nao existe.");
		}
	}

	/**
	 * Retorna a localizacao de um usuario.
	 * 
	 * @param usuario O usuario
	 * @return String com localizacao do usuario
	 * 
	 * @throws SistemaException
	 */
	public String getLocalizacao(String usuario) throws SistemaException {
		try {
			int id = this.getIdPorUserName(usuario);
			return contas.get(id).recolher("localizacao");
		} catch (Exception e) {
			throw new SistemaException("O usuario nao existe.");
		}
	}

	/**
	 * Inicia um sistema de mensagens.
	 * 
	 * @param sistema Sistema de mensagens que se deseja iniciar
	 * 
	 * @throws SistemaException
	 */
	public void initMensageiro(String sistema) throws SistemaException {
		try {
			mensageiro.equals(null);
			if (!mensageiro.equals(sistema)) {
				mensageiro = sistema;

				if (sistema.equals("chat"))
					novoChat = new ServicoMensagemInstantanea();
			}
		} catch (NullPointerException e) {
			mensageiro = sistema;
			if (sistema.equals("chat"))
				novoChat = new ServicoMensagemInstantanea();
		}
	}

	/**
	 * Altera diretorio de gabaritos.
	 * 
	 * @param valor
	 */
	public void setDiretorioGabaritos(String valor) {
		gabaritos = valor;
	}

	/**
	 * Envia convite de localizacao com outro usuario.
	 * 
	 * @param de 
	 * @param para 
	 * @return String com confirmacao de envio
	 * 
	 * @throws IOException
	 */
	public String enviarConvite(String de, String para) throws IOException {
		int id = this.getIdPorUserName(de);
		return contas.get(id).enviarConvite(para);

	}

	/**
	 * Confirma um convite de compartilhamento de localizacao.
	 * 
	 * @param de
	 * @param com usuario que solicitou compartilhamento
	 * @param modo modo de compartilhamento
	 * @param reciproca resposta
	 * 
	 * @throws ContaException
	 */
	public void confirmarCompartilhamento(String de, String com, int modo,
			boolean reciproca) throws ContaException {
		int id = this.getIdPorUserName(com);
		int id2 = this.getIdPorUserName(de);
		contas.get(id).addAmigo(contas.get(getIdPorUserName(de)).recolher("nome"));
		contas.get(id2).addAmigo(contas.get(id).recolher("nome"));
		if (reciproca)
			contas.get(id2)
					.addCompartilharLocalizacao(contas.get(id).recolher("nome"));
		if (modo == 2) {
			contas.get(id)
					.addCompartilharLocalizacao(contas.get(id2).recolher("nome"));

		} else {
			contas.get(id).removeCompartilharLocalizacao(
					contas.get(id2).recolher("nome"));
		}
	}

	/**
	 * Recusa um compartilhamento de localizacao.
	 * 
	 * @param de O remetente
	 * @param com O usuario que solicitou compartilhamento(destinatario)
	 * 
	 * @throws ContaException
	 */
	public void recusarCompartilhamento(String de, String com)
			throws ContaException {
		try {
			contas.get(this.getIdPorUserName(com)).removeContato(de);
			contas.get(this.getIdPorUserName(com))
					.removeCompartilharLocalizacao(de);
		} catch (ContaException e) {

		}
	}

	/**
	 * Obtem lista de amigos do usuario.
	 * 
	 * @param userName
	 * @return String com lista de amigos
	 * 
	 * @throws ContaException
	 */
	public String getAmigos(String userName) throws ContaException {
		try {
			if (!online.contains(userName))
				throw new ContaException("Permissao negada.");
			int id = this.getIdPorUserName(userName);
			return contas.get(id).getAmigos();
		} catch (Exception e) {
			throw new ContaException("Permissao negada.");
		}
	}
	
	public List<String> getListaAmigos(String userName) throws ContaException {
		try {
			if (!online.contains(userName))
				throw new ContaException("Permissao negada.");
			int id = this.getIdPorUserName(userName);
			return contas.get(id).getListaAmigos();
		} catch (Exception e) {
			throw new ContaException("Permissao negada.");
		}
	}

	/**
	 * Retorna localizacao de amigo.
	 * 
	 * @param de
	 * @param amigo
	 * @return String com localizacao
	 * 
	 * @throws ContaException
	 * @throws SistemaException
	 */
	public String getLocalizacaoAmigo(String de, String amigo)
			throws ContaException, SistemaException {
		try {
			if (contas.get(this.getIdPorUserName(de)).possoVerLocalizacao(
					contas.get(this.getIdPorUserName(amigo)).recolher("nome"))) {
				return contas.get(this.getIdPorUserName(amigo))
						.recolher("localizacao");
			} else
				return "Localizacao desconhecida.";
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new SistemaException("Permissao negada.");
		}
	}

	/**
	 * Envia email.
	 * 
	 * @param de
	 * @param para
	 * @param assunto
	 * @param msg
	 * @return String com mensagem de confirmacao de envio
	 * 
	 * @throws MensagemException
	 * @throws IOException
	 */
	public String enviarEmail(String de, String para, String assunto, String msg)
			throws MensagemException, IOException {
		if (mensageiro.equals(Mensagem.tipoEMail)) {
			Mensagem novoEmail;
			novoEmail = new EmailMessage(contas.get(getIdPorUserName(de))
					.recolher("email"), contas.get(getIdPorUserName(para)).recolher("email"),
					assunto, msg);
			salvarEmail((EmailMessage) novoEmail);
			contas.get(this.getIdPorUserName(para)).receberMensagem(novoEmail);
			return "Email enviado com sucesso.";
		}
		throw new MensagemException("Mensageiro nao incializado.");

	}

	/**
	 * Salva o email no log do sistema.
	 * 
	 * @param novoEmail
	 * 
	 * @throws IOException
	 */
	private void salvarEmail(EmailMessage novoEmail) throws IOException {
		FileWriter fw = new FileWriter("emails.log", true);
		fw
				.write("From: "
						+ novoEmail.getRemetente()
						+ "\n"
						+ "to: "
						+ novoEmail.getDestinatario()
						+ "\n"
						+ "Subject: "
						+ novoEmail.getAssunto()
						+ "\n"
						+ "\n"
						+ novoEmail.getTexto()
						+ "\n"
						+ "\n"
						+ "***************************************************************************************************\n");
		fw.close();
	}

	/**
	 * 
	 * @param de
	 * @param para
	 * @param msg
	 * @return mensagem de confirmacao de envio
	 * @throws MensagemException
	 * @throws IOException
	 */
	public String enviarSMS(String de, String para, String msg)
			throws MensagemException, IOException {
		if (mensageiro.equals("SMS")) {
			Mensagem sms = new SMSMessage(contas.get(this.getIdPorUserName(de))
					.recolher("nome"), contas.get(this.getIdPorUserName(para))
					.recolher("telefone"), msg);
			salvarSMS(sms);
			return "SMS enviado com sucesso.";
		}
		throw new MensagemException("Mensageiro nao incializado.");
	}

	/**
	 * Salva o sms no log do sistema
	 * @param sms
	 * @throws IOException
	 */
	private void salvarSMS(Mensagem sms) throws IOException {
		FileWriter fw = new FileWriter("sms.log", true);
		fw
				.write("From: "
						+ sms.getRemetente()
						+ "\n"
						+ "to: "
						+ sms.getDestinatario()
						+ "\n"
						+ "\n"
						+ sms.getTexto()
						+ "\n"
						+ "\n"
						+ "***************************************************************************************************\n");
		fw.close();

	}

	/**
	 * Seta porta de trafego de mensagens
	 * @param port
	 * @throws SistemaException
	 */
	public void setPortChat(int port) throws SistemaException {
		try {
			novoChat.setPorta(port);
		} catch (Exception e) {
			e.printStackTrace();
			throw new SistemaException("Porta invalida.");
		}
	}

	/**
	 * Inicia chat com determinado usuario
	 * @param de
	 * @param para
	 * @throws SistemaException
	 */
	public void initChat(String de, String para) throws SistemaException {
		if (novoChat.equals(null))
			throw new SistemaException("Mensageiro nao criado.");
		if (novoChat.getPorta() == -1)
			throw new SistemaException("Porta invalida.");
		if (!online.contains(para))
			throw new SistemaException("Usuario nao estah online.");
		novoChat.addParticipante(de);
		novoChat.addParticipante(para);
		novoChat.fazNomeLog();

	}

	/**
	 * Envia mensagem do remetente para o destinatario.
	 */
	public void enviarMensagem(String para, String msg) {
		novoChat.addMensage(novoChat.getRemetente(para), msg);
	}

	/**
	 * 
	 * @param de
	 * @param com
	 * @throws IOException
	 */
	public void encerrarChat(String de, String com) throws IOException {
		novoChat.encerrarServico();
		novoChat = null;
	}

	/**
	 * Finaliza o sistema.
	 * 
	 * @throws IOException
	 */
	public void encerrarSistema() throws IOException {
		contasXML.salvar(contas);
	}

	/**
	 * Verifica possibilidade de ver localizacao de amigo
	 * @param usuario
	 * @param amigo
	 * @return True se puder ver, False caso contrario
	 * @throws ContaException
	 * @throws SistemaException
	 */
	public boolean possoVerLocalizacao(String usuario, String amigo)
			throws ContaException, SistemaException {

		if (!online.contains(amigo))
			throw new SistemaException("Usuario desconhecido.");
		try {
			return contas.get(this.getIdPorUserName(usuario))
					.possoVerLocalizacao(
							contas.get(this.getIdPorUserName(amigo)).recolher("nome"));
		} catch (Exception e) {
			throw new ContaException("Usuario desconhecido.");

		}
	}

	/**
	 * 
	 * @param usuario
	 * @param amigo
	 * @throws ContaException
	 */
	public void removerAmigo(String usuario, String amigo)
			throws ContaException {
		try {
			contas.get(this.getIdPorUserName(usuario)).removeContato(
					contas.get(this.getIdPorUserName(amigo)).recolher("nome"));
			contas.get(this.getIdPorUserName(amigo)).removeContato(
					contas.get(this.getIdPorUserName(usuario)).recolher("nome"));
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new ContaException("Permissao negada.");
		}
	}

	/**
	 * 
	 * @param de
	 * @return ultima mensagem enviada
	 */
	public String receberMensagem(String de) {
		return novoChat.getUltimaMensagem();
	}
}
